package cn.rkylin.oms.common.domain;

import cn.rkylin.core.controller.vo.FormSelectVO;
import cn.rkylin.oms.common.function.Function;
import cn.rkylin.oms.common.function.IndexFunction;
import com.alibaba.fastjson.JSONObject;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static cn.rkylin.oms.common.collection.Lists.transform;
import static cn.rkylin.oms.common.domain.ClientSearchCondition.SQLBuilder.toSQLPart;
import static java.lang.Integer.parseInt;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.regex.Matcher.quoteReplacement;
import static org.apache.commons.lang.StringUtils.*;

public class ClientSearchCondition {

    private String keyword;
    private String conditions;

    public String getKeyword() {
        return ParameterDecoder.decode(keyword);
    }

    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }

    public String getConditions() {
        return ParameterDecoder.decode(conditions);
    }

    public void setConditions(String conditions) {
        this.conditions = conditions;
    }

    public String getSQLCondition() throws UnsupportedEncodingException {
        String decoded = getConditions();
        if (isNotBlank(decoded)) {
            List<FormSelectVO> items = JSONObject.parseArray(decoded, FormSelectVO.class);
            if (items != null) {
                return toSQLPart(items);
            }
        }

        return "";
    }

    // WARNING: 有注入风险，当启用allowMultipleQueries时，必须修改。
    static final class SQLBuilder {

        private static Map<String, String> templates = new HashMap<>();
        private static List<String> arrayValueOperators = new ArrayList<>();
        private static List<String> pureValueOperators = new ArrayList<>();

        static {
            templates.put("in",    "{0} in ({1})");
            templates.put("notin", "{0} not in ({1})");
            templates.put("eq",    "{0} = {1}");
            templates.put("neq",   "{0} <> {1}");
            templates.put("gt",    "{0} > {1}");
            templates.put("gte",   "{0} >= {1}");
            templates.put("lt",    "{0} < {1}");
            templates.put("lte",   "{0} <= {1}");
            templates.put("llike", "{0} like '%{1}'");
            templates.put("rlike", "{0} like '{1}%'");
            templates.put("like",  "{0} like '%{1}%'");

            // the following operators need the value convert to array
            arrayValueOperators.add("in");
            arrayValueOperators.add("notin");

            // the following operators need the value without single quotes
            pureValueOperators.add("llike");
            pureValueOperators.add("rlike");
            pureValueOperators.add("like");
        }

        static String toSQLPart(List<FormSelectVO> items) {
            List<String> transformed = transform(items, new IndexFunction<FormSelectVO, String>() {
                @Override
                public String apply(FormSelectVO item, int index) {
                    return item.getOperator1() + " " + toSQLExpression(item);
                }
            });

            return join(transformed, ' ');
        }

        private static String toSQLExpression(FormSelectVO item) {
            String operator = item.getOperator();
            return replace(templates.get(operator), toSQLField(item), toSQLValue(item));
        }

        private static String toSQLField(FormSelectVO item) {
            String field = item.getField();
            int index = field.lastIndexOf('.');
            if (index < 0) {
                return String.format("`%s`", field);
            }

            return field.substring(0, index + 1) + String.format("`%s`", field.substring(index + 1));
        }

        private static String toSQLValue(FormSelectVO item) {
            return arrayValueOperators.contains(item.getOperator()) ? toSQLArrayValue(item) : toSQLValueExpression(item);
        }

        private static String toSQLArrayValue(final FormSelectVO item) {
            String[] splitValues = split(item.getValue(), ",");
            List<String> values = asList(splitValues);
            List<String> transformed = transform(values, new Function<String, String>() {
                @Override
                public String apply(String value) {
                    return toSQLValueExpression(item);
                }
            });
            return join(transformed, ',');
        }

        private static String toSQLValueExpression(FormSelectVO item) {
            String operator = item.getOperator();
            String type = item.getType();
            String value = item.getValue();
            if (pureValueOperators.contains(operator)) {
                return value;
            }

            return "int".equals(type) || "float".equals(type) ? value : format("'%s'", value);
        }

        private static String replace(CharSequence charSequence, String ... values) {
            Matcher matcher = Pattern.compile("\\{(\\d+)}").matcher(charSequence);
            StringBuffer sb = new StringBuffer(charSequence.length());
            while (matcher.find()) {
                matcher.appendReplacement(sb, quoteReplacement(values[parseInt(matcher.group(1))]));
            }

            matcher.appendTail(sb);
            return sb.toString();
        }
    }
}
